-- 2022-8-8

---[[ 打包和解包二进制数据

    -- Lua5.3还引入了一个在二进制数和基本类型(数值和字符串类型)之间进行转换的函数
    -- 函数string.pack会把值"pack"打包为二进制字符串 而函数string.unpack则从字符串中提取这些值

    -- 函数string.pack和函数string.unpack的第一个参数是格式化字符串 用于描述如何打包数据
    -- 格式化字符串中的每个字母都描述了如何打包/解包一个值
    do
        local s = string.pack("iii",3,-27,450)
        print(#s)
        print(string.unpack("iii",s))
    end
    -- 调用函数string.pack将创建一个字符串 其中为3个整型数的二进制码 (根据"iii")
    -- 每一个"i"编码对与之对应的参数进行了编码 而字符串的长度则是一个整型数本身大小的3倍(在笔者的机器上是3x4字节)
    -- 调用函数string.unpack对给定字符串中的3个整型数进行了解码(还是根据"iii")并返回解码后的结果

    -- 为了便于迭代 函数string.unpack还会返回最后一个读取的元素在字符串中的位置
    -- 相应的 该函数还有一个可选的第三个参数 这个参数用于指定开始读取的位置
    do
        local s = "hello\0Lua\0world\0"
        local i = 1
        while i<=#s do
            local res
            res,i=string.unpack("z",s,i)
            print(i)
            print(res)
        end
    end
    -- 正如我们马上要看到的 选项z意味着一个以\0结尾的字符串 因此 调用函数unpack会从s中提取位于i的字符串 并返回该字符串外加循环迭代的下个位置

    -- 对于编码一个整型数而言有几种选项 每一种对应了一种整型大小:b(char) h(short) i(int) l(long) j(代表Lua语言中整型数的大小)
    -- 要是使用固定的 与机器无关的大小 可以在选项i后加上一个1-16的数 例如 i7会产生7字节(7*8=56位)的整型数 所有的大小都会被检查是否存在溢出的情况
    do
        local x = string.pack("i7",1<<54)
        print(string.unpack("i7",x))
        x = string.pack("i7",-(1<<54))
        print(string.unpack("i7",x))
        -- x = string.pack("i7",1<<55) -- bad argument #2 to 'pack' (integer overflow) 报错
        -- 我们可以打包和解包比Lua原生整型更大的整型，但是在解包的时候它们的实际值必须能够被Lua语言的整型容纳
        
        x = string.pack("i12",2^61)
        string.unpack("i12",x)
        x = "aaaaaaaaaaaa"  -- 模拟一个大的十二字节的数
        -- string.unpack("i12",x) -- 12-byte integer does not fit into Lua Integer 报错 实际的x有12字节 大于整型能容纳

        -- 每个针对整型的选项都有一个对应的大写版本，对应相应大写的无符号整型数
        local s = "xFF"
        print(string.unpack("b",s))
        print(string.unpack("B",s))
    end
    -- 同时 无符号整型数对于size_t而言还有一个额外的选项T(size_t类型在ISO C中是一个足够容纳任意大小的无符号整数)

    -- 我们可以用3种表示形式打包字符串:\0结尾的字符串 定长字符串和使用显式长度的字符串
    -- \0结尾的字符串使用选项z 定长字符串使用选项cn 其中n是被打包字符串的字节数
    -- 显示长度的字符串在存储时会在字符串前加上该字符串的长度
    -- 在这种情况下 选项的格式形如sn 其中n是用于保存字符串长度的无符号整型数的大小
    -- 例如 选项s1表示把字符串长度保存在一个字节中
    do
        local s = string.pack("s1","hello")
        for i = 1, #s, 1 do
            print((string.unpack("B",s,i)))
        end
    end
    -- 如果用于保存长度的字节容纳不了字符串长度 那么Lua语言会抛出异常
    -- 我们可以单纯使用选项s 在这种情况下 字符串长度会被以足够容纳任何长度的size_t类型保存
    -- (在64位机器中 size_t通常是8字节的无符号整数 对于较短的字符串来说可能会浪费空间)

    -- 对于浮点型数 有三种选项 f用于单精度浮点数 d用于双精度浮点数 n用于Lua语言浮点数
    -- 格式字符串也有用来控制大小端模式和二进制数据对齐的选项
    -- 在默认情况下 格式使用的是机器原生的大小端模式
    -- 选项 > 把所有后续的编码转换改为大端模式或网络字节序
    do
        local s = string.pack(">i4",1000000)
        for i = 1, #s do
            print(string.unpack("B",s,i))
        end
    end
    -- 选项 < 则改为小端模式
    do
        local s = string.pack("<i2 i2",500,24)
        for i = 1, #s do
            print(string.unpack("B",s,i))
        end
    end
    -- 最后 选项=改回机器默认的原生大小端模式

    -- 对于对齐而言 选项!n强制数据对齐到以n为倍数的索引上
    -- 更准确的说 如果数据比n小 那么对齐到其自身大小上 否则 对齐到n上
    -- 例如 假设格式化字符串为!4 那么1字节整型数会被写入以1为倍数的索引位置上(也就是任意索引位置上)
    -- 2字节的会被写入以2为倍数的索引位置上 而4字节或更大的整型数会被写入以4为倍数的索引位置上
    -- 而选项!(不带数字)则把对齐设为机器默认的对齐方式

    -- 函数string.pack通过在结果字符串到达合适索引值前增加0的方式实现对齐
    -- 函数string.unpack在读取字符串时会简单的跳过这些补位
    -- 对齐只对2的整数次幂有效 如果把对齐设为4但试图操作3字节的整型数 那么Lua语言会抛出异常

    -- 所有的格式化字符串默认带有前缀"=!1" 即表示使用默认的大小端模式且不对齐(因为每个索引都是1的倍数)
    -- 我们可以在程序执行过程中的任意时点改变大小端模式和对齐方式

    -- 如果需要 可以手工添加补位 选项x代表1字节的补位 函数string.pack会在结果字符串中增加一个0 而函数string.unpack则从目标字符串中跳过一字节
--]]